﻿using System;
using System.Collections;
using System.Collections.Generic;
using Needle.Bus.Messages;

namespace Needle.Bus.Engine.Filters
{
    public sealed class FilterChain :
        IHasCount,
        IEnumerable<Predicate<IMessage>>,
        IEquatable<FilterChain>
    {

        private static readonly Predicate<IMessage>[] EmptyChainArray = new Predicate<IMessage>[] { };

        public static readonly FilterChain Empty = new FilterChain();

        private readonly Predicate<IMessage>[] _filters;

        public int Count
        {
            get { return _filters == null ? 0 : _filters.Length; }
        }

        #region constructor

        public FilterChain(params Predicate<IMessage>[] filters)
        { _filters = filters; }

        #endregion

        public bool Filter(IMessage message)
        {
            if (_filters == null)
                return true;

            foreach (var filter in _filters)
            {
                if (filter(message))
                    continue;
                return false;
            }
            return true;
        }

        public IEnumerator<Predicate<IMessage>> GetEnumerator()
        {
            if (_filters == null || _filters.Length == 0)
                yield break;

            foreach (var filter in _filters)
                yield return filter;

            yield break;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public override bool Equals(object obj)
        {
            if (obj is FilterChain)
                return Equals((FilterChain)obj);

            return false;
        }

        public bool Equals(FilterChain other)
        {
            if (Object.ReferenceEquals(other, null)
                || this.Count != other.Count)
                return false;

            using (var me = this.GetEnumerator())
            using (var you = other.GetEnumerator())
            {
                while (me.MoveNext() && you.MoveNext())
                {
                    if (me.Current != you.Current)
                        return false;
                }
                return true;
            }
        }

        public override int GetHashCode()
        {
            int hashcode = 0;

            if (_filters == null)
                return hashcode;

            foreach (var filter in _filters)
                hashcode = ((hashcode >> 25) | (hashcode << 17)) ^ filter.GetHashCode();

            return hashcode;
        }

        public static implicit operator Predicate<IMessage>(FilterChain chain)
        {
            if (chain == null)
                return null;

            return chain.Filter;
        }

        public static implicit operator Predicate<IMessage>[](FilterChain chain)
        {
            if (chain == null || chain.Count == 0)
                return EmptyChainArray;

            return (Predicate<IMessage>[])chain._filters.Clone();
        }

    }
}
